Completed
Push — master ( b3122f...2909e2 )
by MusikAnimal
02:23
created

window.setupMonthYearChart   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
c 1
b 0
f 1
nc 1
dl 0
loc 3
rs 10
nop 1
1
$(function () {
2
    // Don't do anything if this isn't a Edit Counter page.
3
    if ($("body.ec").length === 0) {
4
        return;
5
    }
6
7
    // Set up charts.
8
    $(".chart-wrapper").each(function () {
9
        var chartType = $(this).data("chart-type");
10
        if ( chartType === undefined ) {
11
            return false;
12
        }
13
        var data = $(this).data("chart-data");
14
        var labels = $(this).data("chart-labels");
15
        var $ctx = $("canvas", $(this));
16
17
        /** global: Chart */
18
        new Chart($ctx, {
0 ignored issues
show
Unused Code Best Practice introduced by
The object created with new Chart($ctx, {Identif...e))))),false,false)))}) is not used but discarded. Consider invoking another function instead of a constructor if you are doing this purely for side effects.
Loading history...
19
            type: chartType,
20
            data: {
21
                labels: labels,
22
                datasets: [ { data: data } ]
23
            }
24
        });
25
26
        return undefined;
27
    });
28
29
    loadLatestGlobal();
30
31
    // Set up namespace toggle chart.
32
    setupToggleTable(window.namespaceTotals, window.namespaceChart, null, function (newData) {
33
        var total = 0;
34
        Object.keys(newData).forEach(function (namespace) {
35
            total += parseInt(newData[namespace], 10);
36
        });
37
        var namespaceCount = Object.keys(newData).length;
38
        $('.namespaces--namespaces').text(
39
            namespaceCount.toLocaleString() + " " +
40
            $.i18n('num-namespaces', namespaceCount)
41
        );
42
        $('.namespaces--count').text(total.toLocaleString());
43
    });
44
});
45
46
/**
47
 * Load recent global edits' HTML via AJAX, to not slow down the initial page load.
48
 * Only load if container is present, which is missing in subroutes, e.g. ec-namespacetotals, etc.
49
 */
50
function loadLatestGlobal()
51
{
52
    var $latestGlobalContainer = $("#latestglobal-container");
53
54
    if ($latestGlobalContainer[0]) {
55
        /** global: xtBaseUrl */
56
        var url = xtBaseUrl + 'ec-latestglobal/'
57
            + $latestGlobalContainer.data('project') + '/'
58
            + $latestGlobalContainer.data('username') + '?htmlonly=yes';
59
        $.ajax({
60
            url: url,
61
            timeout: 30000
62
        }).done(function (data) {
63
            $latestGlobalContainer.replaceWith(data);
64
        }).fail(function (_xhr, _status, message) {
65
            $latestGlobalContainer.replaceWith(
66
                $.i18n('api-error', 'Global contributions API: <code>' + message + '</code>')
67
            );
68
        });
69
    }
70
}
71
72
/**
73
 * Set up the monthcounts or yearcounts chart.
74
 * @param {String} id 'year' or 'month'.
75
 * @param {Array} datasets Datasets grouped by mainspace.
76
 * @param {Array} labels The bare labels for the y-axis (years or months).
77
 * @param {Number} maxTotal Maximum value of year/month totals.
78
 */
79
window.setupMonthYearChart = function (id, datasets, labels, maxTotal) {
80
    /**
81
     * Namespaces that have been excluded from view via clickable
82
     * labels above the chart.
83
     * @type {Array}
84
     */
85
    var excludedNamespaces = [];
86
87
    /**
88
     * Number of digits of the max month/year total. We want to keep this consistent
89
     * for aesthetic reasons, even if the updated totals are fewer digits in size.
90
     * @type {Number}
91
     */
92
    var maxDigits = maxTotal.toString().length;
93
94
    /** @type {Array} Labels for each namespace. */
95
    var namespaces = datasets.map(function (dataset) {
96
        return dataset.label;
97
    });
98
99
    /**
100
     * Build the labels for the y-axis of the year/monthcount charts,
101
     * which include the year/month and the total number of edits across
102
     * all namespaces in that year/month.
103
     */
104
    function getYAxisLabels()
105
    {
106
        var labelsAndTotals = {};
107
        datasets.forEach(function (namespace) {
108
            if (excludedNamespaces.indexOf(namespace.label) !== -1) {
109
                return;
110
            }
111
112
            namespace.data.forEach(function (count, index) {
113
                if (!labelsAndTotals[labels[index]]) {
114
                    labelsAndTotals[labels[index]] = 0;
115
                }
116
                labelsAndTotals[labels[index]] += count;
117
            });
118
        });
119
120
        // Format labels with totals next to them. This is a bit hacky,
121
        // but it works! We use tabs (\t) to make the labels/totals
122
        // for each namespace line up perfectly.
123
        // The caveat is that we can't localize the numbers because
124
        // the commas are not monospaced :(
125
        return Object.keys(labelsAndTotals).map(function (year) {
126
            var digitCount = labelsAndTotals[year].toString().length;
127
            var numTabs = (maxDigits - digitCount) * 2;
128
129
            // +5 for a bit of extra spacing.
130
            return year + Array(numTabs + 5).join("\t") +
131
                labelsAndTotals[year];
132
        });
133
    }
134
135
    window[id + 'countsChart'] = new Chart($('#' + id + 'counts-canvas'), {
136
        type: 'horizontalBar',
137
        data: {
138
            labels: getYAxisLabels(),
139
            datasets: datasets
140
        },
141
        options: {
142
            tooltips: {
143
                intersect: true,
144
                callbacks: {
145
                    label: function (tooltip) {
146
                        return tooltip.xLabel.toLocaleString();
147
                    },
148
                    title: function (tooltip) {
149
                        var yLabel = tooltip[0].yLabel.replace(/\t.*/, '');
150
                        return yLabel + ' - ' + namespaces[tooltip[0].datasetIndex];
151
                    }
152
                }
153
            },
154
            responsive: true,
155
            maintainAspectRatio: false,
156
            scales: {
157
                xAxes: [{
158
                    stacked: true,
159
                    ticks: {
160
                        beginAtZero: true,
161
                        callback: function (value) {
162
                            if (Math.floor(value) === value) {
163
                                return value.toLocaleString();
164
                            }
165
                        }
166
                    }
167
                }],
168
                yAxes: [{
169
                    stacked: true
170
                }]
171
            },
172
            legend: {
173
                // Happens when the user enables/disables a namespace via the
174
                // labels above the chart.
175
                onClick: function (e, legendItem) {
176
                    // Update totals, skipping over namespaces that have been excluded.
177
                    if (legendItem.hidden) {
178
                        excludedNamespaces = excludedNamespaces.filter(function (namespace) {
179
                            return namespace !== legendItem.text;
180
                        });
181
                    } else {
182
                        excludedNamespaces.push(legendItem.text);
183
                    }
184
185
                    // Update labels with the new totals.
186
                    window[id + 'countsChart'].config.data.labels = getYAxisLabels();
187
188
                    // Yield to default onClick event, which re-renders the chart.
189
                    Chart.defaults.global.legend.onClick.call(this, e, legendItem);
190
                }
191
            }
192
        }
193
    });
194
}
195